/*!
\copyright  Copyright (c) 2023 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Sync start A2DP - audio sync functionality

*/

#include "sync_start_a2dp_audio_sync.h"
#include "sync_start_a2dp_ctx.h"
#include "sync_start_a2dp_config.h"
#include "sync_start_a2dp_utils.h"
#include "sync_start_a2dp_audio.h"

#include <q2q_mode.h>

#include <kymera_latency_manager.h>

#include <logging.h>
#include <rtime.h>
#include <operators.h>

#include <source.h>
#include <sink.h>
#include <stream.h>

void SyncStartA2dp_AudioSyncConfigureMode(sync_start_a2dp_info_t *sync_info,
                                            mirror_profile_a2dp_start_mode_t a2dp_start_mode)
{
    DEBUG_LOG("appKymeraConfigureAudioSyncMode, a2dp_start_mode: enum:mirror_profile_a2dp_start_mode_t:%d", a2dp_start_mode);

    switch(a2dp_start_mode)
    {
    case MIRROR_PROFILE_A2DP_START_PRIMARY_UNSYNCHRONISED:
        sync_info->mode = KYMERA_AUDIO_SYNC_START_PRIMARY_UNSYNCHRONISED;
        break;

    case MIRROR_PROFILE_A2DP_START_PRIMARY_SYNCHRONISED:
        sync_info->mode = LatencyConfig_GetTwsLatency() < TWS_STANDARD_LATENCY_MS ?
            KYMERA_AUDIO_SYNC_START_PRIMARY_SYNC_UNMUTE:
            KYMERA_AUDIO_SYNC_START_PRIMARY_SYNCHRONISED;
        break;

    case MIRROR_PROFILE_A2DP_START_PRIMARY_SYNC_UNMUTE:
        /* When reconfiguring latency, the devices start approximately in sync
           and latency manager unmutes once in sync. */
        sync_info->mode = Kymera_LatencyManagerIsReconfigInProgress() ?
            KYMERA_AUDIO_SYNC_START_PRIMARY_SYNCHRONISED:
            KYMERA_AUDIO_SYNC_START_PRIMARY_SYNC_UNMUTE;
        break;

    case MIRROR_PROFILE_A2DP_START_SECONDARY_SYNCHRONISED:
        sync_info->mode = LatencyConfig_GetTwsLatency() < TWS_STANDARD_LATENCY_MS ?
            KYMERA_AUDIO_SYNC_START_SECONDARY_SYNC_UNMUTE:
            KYMERA_AUDIO_SYNC_START_SECONDARY_SYNCHRONISED;
        break;

    case MIRROR_PROFILE_A2DP_START_SECONDARY_SYNC_UNMUTE:
        /* When reconfiguring latency, the devices start approximately in sync
           and latency manager unmutes once in sync. */
        sync_info->mode = Kymera_LatencyManagerIsReconfigInProgress() ?
            KYMERA_AUDIO_SYNC_START_SECONDARY_SYNCHRONISED :
            KYMERA_AUDIO_SYNC_START_SECONDARY_SYNC_UNMUTE;
        break;

    case MIRROR_PROFILE_A2DP_START_Q2Q_MODE:
        sync_info->mode = KYMERA_AUDIO_SYNC_START_Q2Q;
        break;

    case MIRROR_PROFILE_A2DP_START_Q2Q_MODE_PRIMARY_UNMUTE:
        sync_info->mode = KYMERA_AUDIO_SYNC_START_Q2Q_PRIMARY_UNMUTE;
         break;

    case MIRROR_PROFILE_A2DP_START_Q2Q_MODE_SECONDARY_UNMUTE:
        sync_info->mode = KYMERA_AUDIO_SYNC_START_Q2Q_SECONDARY_UNMUTE;
        break;

    default:
        Panic();
        break;
    }
}

static void appKymeraDestroyClockConvertTransform(Transform transform)
{
    if (transform)
    {
        DEBUG_LOG("appKymeraDestroyClockConvertTransform, destroy"
                  " convert clock transform:0x%x", transform);
        TransformDisconnect(transform);
    }
}

static Transform appKymeraCreateAndConfigureClockConvertTransform(Source source, Sink sink)
{
    /* Create the transform */
    StreamDisconnect(source, 0);
    Transform cc_transform = TransformConvertClock(source, sink);
    if (cc_transform)
    {
        /* Configure the transform */
        (void) TransformConfigure(cc_transform, VM_TRANSFORM_CLK_CONVERT_START_OFFSET, 8);
        (void) TransformConfigure(cc_transform, VM_TRANSFORM_CLK_CONVERT_REPETITION_OFFSET, 6);
        (void) TransformConfigure(cc_transform, VM_TRANSFORM_CLK_CONVERT_NUM_REPETITIONS, 0xFFFF);
    }
    else
    {
        DEBUG_LOG("appKymeraCreateAndConfigureClockConvertTransform, failed to create transform");
    }
    return cc_transform;
}

inline static void syncStartA2dp_ConfigureClockTransformForPrimary(sync_start_a2dp_ctx_t *ctx, Source op_source, Sink endpoint_sink)
{
    appKymeraDestroyClockConvertTransform(ctx->convert_ttp_to_wc);
    appKymeraDestroyClockConvertTransform(ctx->convert_wc_to_ttp);

    /* Create transform to convert ttp info from local system time into wallclock domain before writing to sink */
    ctx->convert_ttp_to_wc = appKymeraCreateAndConfigureClockConvertTransform(op_source,
                                                                                    endpoint_sink);
    /* Create transform to convert ttp info from wallclock into local system time domain before writing to sink */
    ctx->convert_wc_to_ttp = appKymeraCreateAndConfigureClockConvertTransform(StreamSourceFromSink(endpoint_sink),
                                                                                    StreamSinkFromSource(op_source));
    (void) SourceConfigure(op_source,
                           STREAM_AUDIO_SYNC_SOURCE_INTERVAL,
                           AUDIO_SYNC_MS_INTERVAL * US_PER_MS);

    SyncStartA2dp_ConfigureAudioSyncLatency(op_source);

    (void) SourceConfigure(op_source,
                           STREAM_AUDIO_SYNC_SOURCE_MTU,
                           AUDIO_SYNC_PACKET_MTU);
}

inline static void syncStartA2dp_ConfigureClockTransformForSecondary(sync_start_a2dp_ctx_t *ctx, Source op_source, Sink endpoint_sink)
{
    Sink sync_sink = StreamSinkFromSource(op_source);

    appKymeraDestroyClockConvertTransform(ctx->convert_ttp_to_wc);
    appKymeraDestroyClockConvertTransform(ctx->convert_wc_to_ttp);

    /* set audio sync sink data mode to process ttp_info data
     * received from secondary (old primary) */
    (void) SinkConfigure(sync_sink,
                         STREAM_AUDIO_SYNC_SINK_MODE,
                         SINK_MODE_STARTUP);

    /* Setting source MTU is harmless and avoids it's
     * configuration during handover */
    (void) SourceConfigure(op_source,
                           STREAM_AUDIO_SYNC_SOURCE_MTU,
                           AUDIO_SYNC_PACKET_MTU);

    /* Create transform to convert ttp info from local system time into wallclock domain before writing to sink */
    ctx->convert_wc_to_ttp = appKymeraCreateAndConfigureClockConvertTransform(op_source,
                                                                                    endpoint_sink);
    /* Create transform to convert ttp info from wallclock to local system time domain before writing to sink */
    ctx->convert_ttp_to_wc = appKymeraCreateAndConfigureClockConvertTransform(StreamSourceFromSink(endpoint_sink),
                                                                                sync_sink);
}

inline static void syncStartA2dp_StartTransforms(sync_start_a2dp_ctx_t *ctx)
{
    if( ctx->convert_ttp_to_wc)
    {
        if (TransformStart(ctx->convert_ttp_to_wc))
        {
            DEBUG_LOG("appKymeraCreateAndConfigureAudioSync, started ttp_to_wc transform:0x%x", ctx->convert_ttp_to_wc);
        }
        else
        {
            DEBUG_LOG("appKymeraCreateAndConfigureAudioSync, failed starting ttp_to_wc transform:0x%x", ctx->convert_ttp_to_wc);
        }
    }

    if( ctx->convert_wc_to_ttp)
    {
        if (TransformStart(ctx->convert_wc_to_ttp))
        {
            DEBUG_LOG("appKymeraCreateAndConfigureAudioSync, started wc_to_ttp transform:0x%x", ctx->convert_wc_to_ttp);
        }
        else
        {
            DEBUG_LOG("appKymeraCreateAndConfigureAudioSync, failed starting wc_to_ttp transform:0x%x", ctx->convert_wc_to_ttp);
        }
    }
}

void SyncStartA2dp_AudioSyncCreateAndConfigure(sync_start_a2dp_info_t *sync_info, Sink endpoint_sink, Operator op_rtp)
{
    sync_start_a2dp_ctx_t *ctx = SyncStartA2dp_GetCtx();

    if (Q2qMode_IsEnabled())
    {
        DEBUG_LOG("appKymeraCreateAndConfigureAudioSync, q2q mode doing nothing");
        return;
    }

    if (op_rtp)
    {
        sync_info->source = PanicNull(StreamAudioSyncSource(op_rtp));

        MessageStreamTaskFromSource(sync_info->source, SyncStartA2dp_GetTask());
        MessageStreamTaskFromSink(StreamSinkFromSource(sync_info->source), SyncStartA2dp_GetTask());

        DEBUG_LOG("appKymeraCreateAndConfigureAudioSync, created source:0x%x,"
                " mode enum:appKymeraAudioSyncStartMode:%d", sync_info->source, sync_info->mode);
        switch(sync_info->mode)
        {
            case KYMERA_AUDIO_SYNC_START_PRIMARY_SYNCHRONISED:
            case KYMERA_AUDIO_SYNC_START_PRIMARY_SYNC_UNMUTE:
            {
                syncStartA2dp_ConfigureClockTransformForPrimary(ctx, sync_info->source, endpoint_sink);
            }
            break;

            case KYMERA_AUDIO_SYNC_START_PRIMARY_UNSYNCHRONISED:
                /* nothing to be done for now */
            break;

            case KYMERA_AUDIO_SYNC_START_SECONDARY_SYNC_UNMUTE:
            case KYMERA_AUDIO_SYNC_START_SECONDARY_SYNCHRONISED:
            {
                syncStartA2dp_ConfigureClockTransformForSecondary(ctx, sync_info->source, endpoint_sink);
            }
            break;

            case KYMERA_AUDIO_SYNC_START_Q2Q: /* should never hit this Q2Q option */
            default:
                Panic();
            break;
        }

        OperatorsRtpSetTtpNotification(op_rtp, TRUE);

        syncStartA2dp_StartTransforms(ctx);
    }
}

void SyncStartA2dp_AudioSyncDestroy(sync_start_a2dp_ctx_t *ctx, Source endpoint_source, Operator op_rtp)
{
    if (Q2qMode_IsEnabled())
    {
        DEBUG_LOG("appKymeraStopAudioSync, q2q mode doing nothing");
        return;
    }

    PanicZero(ctx->sync_info.source);

    MessageStreamTaskFromSource(ctx->sync_info.source, NULL);

    appKymeraDestroyClockConvertTransform(ctx->convert_ttp_to_wc);
    ctx->convert_ttp_to_wc = NULL;

    appKymeraDestroyClockConvertTransform(ctx->convert_wc_to_ttp);
    ctx->convert_wc_to_ttp = NULL;

    if(op_rtp != INVALID_OPERATOR)
    {
        OperatorsRtpSetTtpNotification(op_rtp, FALSE);
    }

    SourceClose(ctx->sync_info.source);
    DEBUG_LOG("appKymeraStopAudioSync, closed source:0x%x", ctx->sync_info.source);

    if (endpoint_source)
    {
        /* Disconnect source from any connection and dispose data.
        This is required if primary starts to send audio sync messages
        when secondary is not is A2DP streaming state */
        StreamDisconnect(endpoint_source, 0);
        StreamConnectDispose(endpoint_source);
    }

    ctx->sync_info.source = 0;
}

void SyncStartA2dp_AudioSyncPause(void)
{
    sync_start_a2dp_ctx_t *ctx = SyncStartA2dp_GetCtx();
    
    /* Disabling audio sync adjustments */
    switch(ctx->sync_info.mode)
    {
    case KYMERA_AUDIO_SYNC_START_PRIMARY_SYNCHRONISED:
    case KYMERA_AUDIO_SYNC_START_PRIMARY_SYNC_UNMUTE:
    {
        PanicZero(ctx->sync_info.source);
        DEBUG_LOG("SyncStartA2dp_AudioSyncPause, reset audio_sync source interval");
        /* reset audio sync source interval to stop transfer
            * of ttp_info to secondary during handover.
            */
            (void) SourceConfigure(ctx->sync_info.source,
                                STREAM_AUDIO_SYNC_SOURCE_INTERVAL, 0);
    }
    break;
    case KYMERA_AUDIO_SYNC_START_SECONDARY_SYNCHRONISED:
    case KYMERA_AUDIO_SYNC_START_SECONDARY_SYNC_UNMUTE:
    {
        PanicZero(ctx->sync_info.source);
        DEBUG_LOG("SyncStartA2dp_AudioSyncPause, set audio_sync sink to ignore data");
        /* set audio sync sink data mode to ignore inflight ttp_info data
            * received from primary during handover
            */
        (void) SinkConfigure(StreamSinkFromSource(ctx->sync_info.source),
                                STREAM_AUDIO_SYNC_SINK_MODE,
                                SINK_MODE_DEFAULT);
    }
    break;
    default:
        break;
    }
}

void SyncStartA2dp_AudioSyncResume(void)
{
    sync_start_a2dp_ctx_t *ctx = SyncStartA2dp_GetCtx();

    PanicZero(ctx->sync_info.source);
    switch(ctx->sync_info.mode)
    {
    case KYMERA_AUDIO_SYNC_START_PRIMARY_SYNCHRONISED:
    case KYMERA_AUDIO_SYNC_START_PRIMARY_SYNC_UNMUTE:
    {
        DEBUG_LOG("SyncStartA2dp_AudioSyncResume, set audio_sync source interval");
        /* set audio sync source interval to resume transfer
        * of ttp_info to secondary.
        */
        (void) SourceConfigure(ctx->sync_info.source,
                                STREAM_AUDIO_SYNC_SOURCE_INTERVAL,
                                AUDIO_SYNC_MS_INTERVAL  * US_PER_MS);
    }
    break;
    case KYMERA_AUDIO_SYNC_START_SECONDARY_SYNCHRONISED:
    case KYMERA_AUDIO_SYNC_START_SECONDARY_SYNC_UNMUTE:
    {
        DEBUG_LOG("SyncStartA2dp_AudioSyncResume, set audio_sync sink mode to process data");
        /* set audio sync sink data mode to ignore inflight ttp_info data
        * received from primary during handover
        */
        (void) SinkConfigure(StreamSinkFromSource(ctx->sync_info.source),
                            STREAM_AUDIO_SYNC_SINK_MODE,
                            SINK_MODE_STARTUP);
    }
    break;
    default:
        break;
    }
}

void SyncStartA2dp_AudioSyncStart(bool is_primary, uint32_t latency_in_us)
{
    sync_start_a2dp_ctx_t *ctx = SyncStartA2dp_GetCtx();

    PanicZero(ctx->sync_info.source);
    PanicZero(ctx->rtp_op);

    if (is_primary)
    {
        ctx->sync_info.mode = KYMERA_AUDIO_SYNC_START_PRIMARY_SYNCHRONISED;

        /* switch audio RTP operator in normal (or TTP_FULL) mode */
        DEBUG_LOG("SyncStartA2dp_AudioSyncStart, configure RTP operator in ttp_full_only mode");
        OperatorsStandardSetTtpState(ctx->rtp_op, ttp_full_only, 0, 0, latency_in_us);

        /* set audio sync source interval to start transfer
            * of ttp_info to secondary (old primary). */
        (void) SourceConfigure(ctx->sync_info.source,
                                STREAM_AUDIO_SYNC_SOURCE_INTERVAL,
                                AUDIO_SYNC_MS_INTERVAL  * US_PER_MS);

        /* set audio sync sink data mode to ignore inflight ttp_info data
            * received from secondary (old primary) during handover */
        (void) SinkConfigure(StreamSinkFromSource(ctx->sync_info.source),
                                STREAM_AUDIO_SYNC_SINK_MODE,
                                SINK_MODE_DEFAULT);
    }
    else
    {
        ctx->sync_info.mode = KYMERA_AUDIO_SYNC_START_SECONDARY_SYNCHRONISED;

        /* switch audio RTP operator in free_run mode */
        DEBUG_LOG("SyncStartA2dp_AudioSyncStart, configure RTP operator in ttp_free_run_only mode");
        OperatorsStandardSetTtpState(ctx->rtp_op, ttp_free_run_only, 0, 0, latency_in_us);

        /* set audio sync source interval to zero to stop transfer of
            * ttp_info to primary (old secondary). */
        (void) SourceConfigure(ctx->sync_info.source,
                                STREAM_AUDIO_SYNC_SOURCE_INTERVAL,
                                0);
        /* set audio sync sink data mode to process ttp_info data
            * received from secondary (old primary) */
        (void) SinkConfigure(StreamSinkFromSource(ctx->sync_info.source),
                                STREAM_AUDIO_SYNC_SINK_MODE,
                                SINK_MODE_STARTUP);
    }
}
